/*
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 *    DefaultPackage.java
 *    Copyright (C) 2015 University of Waikato, Hamilton, New Zealand
 *
 */
package weka.core.packageManagement;

import java.io.File;
import java.io.Serializable;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

/**
 * A concrete implementation of Package that uses Java properties files/classes
 * to manage package meta data. Assumes that meta data for individual packages
 * is stored on the central repository (or possibly in a local cache - both
 * accessible via http) in properties files that live in a sub-directory with
 * the same name as the package. Furthermore, each property file is assumed to
 * be named as the version number of the package in question with a ".props"
 * extension. A "Latest.props" file should exist for each package and should
 * always hold meta data on the latest version of a package.
 * 
 * @author mhall (mhall{[at]}pentaho{[dot]}com).
 * @version $Revision: 52462 $
 * 
 */
public class DefaultPackage extends Package implements Serializable {

    /**
     * For serialization
     */
    private static final long serialVersionUID = 3643121886457892125L;

    /** Holds the home directory for installed packages */
    protected File m_packageHome;

    /** The package manager in use */
    protected transient PackageManager m_packageManager;

    /**
     * Clone this package. Only makes a shallow copy of the meta data map
     * 
     * @return a copy of this package
     */
    @Override
    public Object clone() {

        DefaultPackage newP = null;

        if (m_packageHome != null) {
            newP = new DefaultPackage(new File(m_packageHome.toString()), m_packageManager);
        } else {
            newP = new DefaultPackage(null, m_packageManager);
        }

        HashMap<Object, Object> metaData = new HashMap<Object, Object>();
        Set<?> keys = m_packageMetaData.keySet();
        Iterator<?> i = keys.iterator();

        while (i.hasNext()) {
            Object key = i.next();
            Object value = m_packageMetaData.get(key);
            metaData.put(key, value);
        }

        newP.setPackageMetaData(metaData);

        return newP;
    }

    /**
     * Set the package manager for this package
     * 
     * @param p the package manager to use
     */
    public void setPackageManager(PackageManager p) {
        m_packageManager = p;
    }

    /**
     * Constructs an new DefaultPackage.
     * 
     * @param packageHome     the directory that packages are installed into.
     * @param manager         the package manager in use.
     * @param packageMetaData A Map of package meta data for this package.
     */
    public DefaultPackage(File packageHome, PackageManager manager, Map<?, ?> packageMetaData) {
        this(packageHome, manager);

        setPackageMetaData(packageMetaData);
    }

    /**
     * Constructs a new DefaultPackage.
     * 
     * @param packageHome the directory that packages are installed into.
     * @param manager     the package manager in use.
     */
    public DefaultPackage(File packageHome, PackageManager manager) {
        m_packageHome = packageHome;
        m_packageManager = manager;
    }

    /**
     * Convenience method that returns the URL to the package (i.e the provider's
     * URL). This information is assumed to be stored in the package meta data.
     * 
     * @return the URL to the package or null if the URL is not available for some
     *         reason
     * @throws Exception if the URL can't be retrieved for some reason
     */
    @Override
    public URL getPackageURL() throws Exception {
        String url = getPackageMetaDataElement("PackageURL").toString().trim();

        URL packageURL = new URL(url);

        return packageURL;
    }

    /**
     * Convenience method to return the name of this package.
     * 
     * @return the name of this package.
     */
    @Override
    public String getName() {
        return getPackageMetaDataElement("PackageName").toString();
    }

    protected static String[] splitNameVersion(String nameAndVersion) {
        String[] result = new String[3];

        nameAndVersion = nameAndVersion.trim();
        if (nameAndVersion.indexOf('(') < 0) {
            result[0] = nameAndVersion;
        } else if (nameAndVersion.indexOf(')') >= 0) {
            boolean ok = true;
            result[0] = nameAndVersion.substring(0, nameAndVersion.indexOf('('));
            result[0] = result[0].trim();

            String secondInequality = null;
            int delimiterIndex = nameAndVersion.indexOf('|');
            if (delimiterIndex >= 0) {
                secondInequality = nameAndVersion.substring(delimiterIndex + 1, nameAndVersion.length());
                secondInequality = secondInequality.trim();
                String[] result2 = new String[5];
                result2[0] = result[0];
                result = result2;
            } else {
                delimiterIndex = nameAndVersion.length();
            }

            nameAndVersion = nameAndVersion.substring(nameAndVersion.indexOf('(') + 1, delimiterIndex);
            nameAndVersion = nameAndVersion.trim();
            int pos = 1;
            if (nameAndVersion.charAt(0) == '=') {
                result[1] = "=";
            } else if (nameAndVersion.charAt(1) == '=') {
                pos++;
                if (nameAndVersion.charAt(0) == '<') {
                    result[1] = "<=";
                } else {
                    result[1] = ">=";
                }
            } else if (nameAndVersion.charAt(0) == '<') {
                result[1] = "<";
            } else if (nameAndVersion.charAt(0) == '>') {
                result[1] = ">";
            } else {
                ok = false;
            }

            if (ok) {
                if (secondInequality != null) {
                    delimiterIndex = nameAndVersion.length();
                } else {
                    delimiterIndex = nameAndVersion.indexOf(')');
                }
                nameAndVersion = nameAndVersion.substring(pos, delimiterIndex);
                result[2] = nameAndVersion.trim();
            }

            // do the second inequality (if present)
            if (secondInequality != null) {
                ok = true;
                pos = 1;
                if (secondInequality.charAt(0) == '=') {
                    result[3] = "=";
                } else if (secondInequality.charAt(1) == '=') {
                    pos++;
                    if (secondInequality.charAt(0) == '<') {
                        result[3] = "<=";
                    } else {
                        result[3] = ">=";
                    }
                } else if (secondInequality.charAt(0) == '<') {
                    result[3] = "<";
                } else if (secondInequality.charAt(0) == '>') {
                    result[3] = ">";
                } else {
                    ok = false;
                }

                if (ok) {
                    secondInequality = secondInequality.substring(pos, secondInequality.indexOf(')'));
                    result[4] = secondInequality.trim();
                }
            }
        }

        return result;
    }

    /**
     * Get the list of packages that this package depends on.
     * 
     * @return the list of packages that this package depends on.
     * @throws Exception if a problem occurs while getting the list of dependencies.
     */
    @Override
    public List<Dependency> getDependencies() throws Exception {
        List<Dependency> dependencies = new ArrayList<Dependency>();
        String dependenciesS = getPackageMetaDataElement("Depends").toString();

        if (dependenciesS != null) {
            StringTokenizer tok = new StringTokenizer(dependenciesS, ",");
            while (tok.hasMoreTokens()) {
                String nextT = tok.nextToken().trim();
                String[] split = splitNameVersion(nextT);
                Package toAdd = null;

                // don't include the base system!
                if (!(split[0].equalsIgnoreCase(m_packageManager.getBaseSystemName()))) {

                    // gets the latest version of this package if split[2] is null
                    toAdd = m_packageManager.getRepositoryPackageInfo(split[0], split[2]);

                    if (split.length == 3) {
                        VersionPackageConstraint versionConstraint = new VersionPackageConstraint(toAdd);
                        if (split[2] == null) {
                            // assume anything up to and including the current version is
                            // acceptable
                            versionConstraint.setVersionConstraint(VersionPackageConstraint.VersionComparison.LESSTHANOREQUAL);
                        } else {
                            versionConstraint.setVersionConstraint(split[1]);
                        }

                        Dependency dep = new Dependency(this, versionConstraint);
                        dependencies.add(dep);
                    } else {
                        // ranged constraint
                        VersionRangePackageConstraint versionConstraint = new VersionRangePackageConstraint(toAdd);
                        VersionPackageConstraint.VersionComparison comp1 = VersionPackageConstraint.getVersionComparison(split[1]);
                        VersionPackageConstraint.VersionComparison comp2 = VersionPackageConstraint.getVersionComparison(split[3]);
                        versionConstraint.setRangeConstraint(split[2], comp1, split[4], comp2);

                        Dependency dep = new Dependency(this, versionConstraint);
                        dependencies.add(dep);
                    }
                }
            }
        }

        return dependencies;
    }

    /**
     * Gets the dependency on the base system that this package requires.
     * 
     * @return the base system dependency(s) for this package
     * @throws Exception if the base system dependency can't be determined for some
     *                   reason.
     */
    @Override
    public List<Dependency> getBaseSystemDependency() throws Exception {
        String dependenciesS = getPackageMetaDataElement("Depends").toString();
        Dependency baseDep = null;
        List<Dependency> baseDeps = new ArrayList<Dependency>();

        if (dependenciesS != null) {
            StringTokenizer tok = new StringTokenizer(dependenciesS, ",");
            while (tok.hasMoreTokens()) {
                String nextT = tok.nextToken().trim();
                String[] split = splitNameVersion(nextT);

                if ((split[0].equalsIgnoreCase(m_packageManager.getBaseSystemName()))) {

                    // construct a "dummy" package for the base system
                    Map<String, String> baseMap = new HashMap<String, String>();
                    baseMap.put("PackageName", "weka");

                    // use a suitably ridiculous max version number if one hasn't been
                    // supplied
                    // for some reason
                    split[2] = (split[2] == null ? "1000.1000.1000" : split[2]);
                    baseMap.put("Version", split[2]);
                    Package basePackage = new DefaultPackage(null, m_packageManager, baseMap);

                    if (split.length == 3) {
                        VersionPackageConstraint baseConstraint = new VersionPackageConstraint(basePackage);
                        VersionPackageConstraint.VersionComparison baseComp = VersionPackageConstraint.VersionComparison.LESSTHANOREQUAL;
                        if (split[1] != null) {
                            baseComp = VersionPackageConstraint.getVersionComparison(split[1]);
                        }
                        baseConstraint.setVersionConstraint(baseComp);

                        baseDep = new Dependency(this, baseConstraint);
                        baseDeps.add(baseDep);
                    } else {
                        // ranged constraint
                        VersionRangePackageConstraint baseConstraint = new VersionRangePackageConstraint(basePackage);

                        VersionPackageConstraint.VersionComparison comp1 = VersionPackageConstraint.getVersionComparison(split[1]);
                        VersionPackageConstraint.VersionComparison comp2 = VersionPackageConstraint.getVersionComparison(split[3]);
                        baseConstraint.setRangeConstraint(split[2], comp1, split[4], comp2);

                        baseDep = new Dependency(this, baseConstraint);
                        baseDeps.add(baseDep);
                    }
                }
            }
        }

        if (baseDeps.size() == 0) {
            throw new Exception("[Package] " + getPackageMetaDataElement("PackageName").toString() + " can't determine what version of the base system is required!!");
        }

        return baseDeps;
    }

    private boolean findPackage(String packageName, List<Package> packageList) {
        boolean found = false;

        Iterator<Package> i = packageList.iterator();

        while (i.hasNext()) {
            Package p = i.next();
            String pName = p.getPackageMetaDataElement("PackageName").toString();
            if (packageName.equals(pName)) {
                found = true;
                break;
            }
        }

        return found;
    }

    /**
     * Gets a list of packages that this package depends on that are not in the
     * supplied list of packages.
     * 
     * @param packages a list of packages to compare this package's dependencies
     *                 against.
     * @return those packages that this package depends on that aren't in the
     *         supplied list.
     * @throws Exception if the list of missing depenencies can't be determined for
     *                   some reason.
     */
    @Override
    public List<Dependency> getMissingDependencies(List<Package> packages) throws Exception {
        List<Dependency> missing = new ArrayList<Dependency>();
        String dependencies = getPackageMetaDataElement("Depends").toString();

        if (dependencies != null) {
            StringTokenizer tok = new StringTokenizer(dependencies, ",");
            while (tok.hasMoreTokens()) {
                String nextT = tok.nextToken().trim();
                String[] split = splitNameVersion(nextT);

                // don't consider the base system!
                if (!(split[0].equalsIgnoreCase(m_packageManager.getBaseSystemName()))) {

                    // gets the latest version of this package if split[2] is null
                    Package tempDep = m_packageManager.getRepositoryPackageInfo(split[0], split[2]);
                    if (!findPackage(split[0], packages)) {
                        VersionPackageConstraint versionConstraint = new VersionPackageConstraint(tempDep);
                        if (split[2] == null) {
                            // assume anything up to and including the current version is
                            // acceptable
                            versionConstraint.setVersionConstraint(VersionPackageConstraint.VersionComparison.LESSTHANOREQUAL);
                            missing.add(new Dependency(this, versionConstraint));
                        } else {
                            if (split.length == 3) {
                                versionConstraint.setVersionConstraint(split[1]);
                                missing.add(new Dependency(this, versionConstraint));
                            } else {
                                VersionRangePackageConstraint versionRConstraint = new VersionRangePackageConstraint(tempDep);
                                VersionPackageConstraint.VersionComparison comp1 = VersionPackageConstraint.getVersionComparison(split[1]);
                                VersionPackageConstraint.VersionComparison comp2 = VersionPackageConstraint.getVersionComparison(split[3]);

                                versionRConstraint.setRangeConstraint(split[2], comp1, split[4], comp2);
                                missing.add(new Dependency(this, versionRConstraint));
                            }
                        }
                    }
                }
            }
        }

        return missing;
    }

    /**
     * Gets a list of packages that this package depends on that are not currently
     * installed.
     * 
     * @return a list of missing packages that this package depends on.
     */
    @Override
    public List<Dependency> getMissingDependencies() throws Exception {
        List<Package> installedPackages = m_packageManager.getInstalledPackages();
        String dependencies = getPackageMetaDataElement("Depends").toString();

        return getMissingDependencies(installedPackages);

        /*
         * if (dependencies != null) { StringTokenizer tok = new
         * StringTokenizer(dependencies, ","); while (tok.hasMoreTokens()) { String
         * nextT = tok.nextToken().trim(); String[] split = splitNameVersion(nextT);
         * 
         * // don't consider the base system! if
         * (!(split[0].equalsIgnoreCase(m_packageManager.getBaseSystemName()))) {
         * 
         * // gets the latest version of this package if split[2] is null Package
         * tempDep = m_packageManager.getRepositoryPackageInfo(split[0], split[2]); if
         * (!tempDep.isInstalled()) { VersionPackageConstraint versionConstraint = new
         * VersionPackageConstraint(tempDep); if (split[2] == null) { // assume anything
         * up to and including the current version is acceptable versionConstraint.
         * setVersionConstraint(VersionPackageConstraint.VersionComparison
         * .LESSTHANOREQUAL); } else { versionConstraint.setVersionConstraint(split[1]);
         * }
         * 
         * missing.add(new Dependency(this, versionConstraint)); } } } }
         * 
         * return missing;
         */
    }

    /**
     * Compares this package's precluded list (if any) against the list of supplied
     * packages. Any packages in the supplied list that match (by name and version
     * constraints) any in the precluded list are returned
     *
     * @param packages a list of packages to compare against those in this package's
     *                 precluded list
     * @return a list of packages that are covered by those in the precluded list
     * @throws Exception if a problem occurs
     */
    @Override
    public List<Package> getPrecludedPackages(List<Package> packages) throws Exception {
        List<Package> result = new ArrayList<>();
        Object precluded = getPackageMetaDataElement("Precludes");
        if (precluded != null) {
            StringTokenizer tok = new StringTokenizer(precluded.toString(), ",");
            while (tok.hasMoreTokens()) {
                String nextT = tok.nextToken().trim();
                String[] splitD = splitNameVersion(nextT);
                // look for a match
                for (Package p : packages) {
                    if (p.getName().equalsIgnoreCase(splitD[0].trim())) {
                        // if a version number is supplied then check, otherwise assume
                        // that version does not matter
                        if (splitD[1] != null && splitD[2] != null) {
                            String versionI = p.getPackageMetaDataElement("Version").toString().trim();

                            if (splitD.length == 3) {
                                VersionPackageConstraint.VersionComparison constraint = VersionPackageConstraint.getVersionComparison(splitD[1]);
                                if (VersionPackageConstraint.checkConstraint(versionI, constraint, splitD[2])) {
                                    result.add(p);
                                }
                            } else {
                                VersionRangePackageConstraint versionRConstraint = new VersionRangePackageConstraint(p);
                                VersionPackageConstraint.VersionComparison comp1 = VersionPackageConstraint.getVersionComparison(splitD[1]);
                                VersionPackageConstraint.VersionComparison comp2 = VersionPackageConstraint.getVersionComparison(splitD[3]);
                                versionRConstraint.setRangeConstraint(splitD[2], comp1, splitD[4], comp2);
                                if (versionRConstraint.checkConstraint(p)) {
                                    result.add(p);
                                }
                            }
                        } else {
                            result.add(p);
                        }
                    }
                }

            }
        }

        return result;
    }

    /**
     * Gets those packages from the supplied list that this package depends on and
     * are currently incompatible with this package.
     * 
     * @param packages a list of packages to compare this package's dependencies
     *                 against
     * @return those packages from the supplied list that are incompatible with
     *         respect to this package's dependencies
     * @throws Exception if the list of incompatible dependencies can't be generated
     *                   for some reason.
     */
    @Override
    public List<Dependency> getIncompatibleDependencies(List<Package> packages) throws Exception {
        List<Dependency> incompatible = new ArrayList<Dependency>();
        String dependencies = getPackageMetaDataElement("Depends").toString();

        if (dependencies != null) {
            StringTokenizer tok = new StringTokenizer(dependencies, ",");
            while (tok.hasMoreTokens()) {
                String nextT = tok.nextToken().trim();
                String[] splitD = splitNameVersion(nextT);

                // check only if a version number was supplied in the dependency list.
                if (splitD[1] != null && splitD[2] != null) {
                    for (Package p : packages) {
                        String packageNameI = p.getPackageMetaDataElement("PackageName").toString();
                        if (packageNameI.trim().equalsIgnoreCase(splitD[0].trim())) {
                            // now check version against this one
                            String versionI = p.getPackageMetaDataElement("Version").toString().trim();
                            // String[] splitI = splitNameVersion(versionI);

                            if (splitD.length == 3) {
                                VersionPackageConstraint.VersionComparison constraint = VersionPackageConstraint.getVersionComparison(splitD[1]);
                                if (!VersionPackageConstraint.checkConstraint(versionI, constraint, splitD[2])) {
                                    VersionPackageConstraint vpc = new VersionPackageConstraint(p);
                                    vpc.setVersionConstraint(constraint);
                                    incompatible.add(new Dependency(this, vpc));
                                }
                            } else {
                                VersionRangePackageConstraint versionRConstraint = new VersionRangePackageConstraint(p);
                                VersionPackageConstraint.VersionComparison comp1 = VersionPackageConstraint.getVersionComparison(splitD[1]);
                                VersionPackageConstraint.VersionComparison comp2 = VersionPackageConstraint.getVersionComparison(splitD[3]);

                                versionRConstraint.setRangeConstraint(splitD[2], comp1, splitD[4], comp2);
                                incompatible.add(new Dependency(this, versionRConstraint));
                            }

                            /*
                             * int comparisonResult = VersionPackageConstraint.compare(versionI, splitD[2]);
                             * if (!versionOK(splitD[1], comparisonResult)) { incompatible.add(p); }
                             */
                        }
                    }
                }
            }
        }

        return incompatible;
    }

    /**
     * Gets a list of installed packages that this package depends on that are
     * currently incompatible with this package.
     * 
     * @return a list of incompatible installed packages that this package depends
     *         on.
     */
    @Override
    public List<Dependency> getIncompatibleDependencies() throws Exception {
        List<Package> installedP = m_packageManager.getInstalledPackages();
        String dependencies = getPackageMetaDataElement("Depends").toString();

        return getIncompatibleDependencies(installedP);

        /*
         * if (dependencies != null) { StringTokenizer tok = new
         * StringTokenizer(dependencies, ","); while (tok.hasMoreTokens()) { String
         * nextT = tok.nextToken().trim(); String[] splitD = splitNameVersion(nextT);
         * 
         * // check only if a version number was supplied in the dependency list. if
         * (splitD[1] != null && splitD[2] != null) { for (Package p : installedP) {
         * String packageNameI = p.getPackageMetaDataElement("PackageName").toString();
         * if (packageNameI.trim().equalsIgnoreCase(splitD[0].trim())) { // now check
         * version against installed String versionI =
         * p.getPackageMetaDataElement("Version").toString().trim(); // String[] splitI
         * = splitNameVersion(versionI);
         * 
         * VersionPackageConstraint.VersionComparison constraint =
         * VersionPackageConstraint.getVersionComparison(splitD[1]); if
         * (!VersionPackageConstraint.checkConstraint(versionI, constraint, splitD[2]))
         * { VersionPackageConstraint vpc = new VersionPackageConstraint(p);
         * vpc.setVersionConstraint(constraint); incompatible.add(new Dependency(this,
         * vpc)); } } } } } }
         * 
         * return incompatible;
         */
    }

    /*
     * public Object getPackageDependencyVersion(String dependsOnName) { String
     * dependencies = getPackageMetaDataElement("Depends").toString(); Object result
     * = null;
     * 
     * if (dependencies != null) { StringTokenizer tok = new
     * StringTokenizer(dependencies, ","); while (tok.hasMoreTokens()) { String
     * nextT = tok.nextToken().trim(); String[] splitD = splitNameVersion(nextT); if
     * (dependsOnName.trim().equalsIgnoreCase(splitD[0]) && splitD[2] != null) {
     * result = splitD[2]; break; } } }
     * 
     * return result; }
     */

    /**
     * Returns true if this package is compatible with the currently installed
     * version of the base system.
     * 
     * @return true if this package is compatible with the main software system.
     * @throws Exception if a problem occurs while checking compatibility.
     */
    @Override
    public boolean isCompatibleBaseSystem() throws Exception {

        String baseSystemName = m_packageManager.getBaseSystemName();
        String systemVersion = m_packageManager.getBaseSystemVersion().toString();
        // System.err.println("Base system version " + systemVersion);

        String dependencies = getPackageMetaDataElement("Depends") == null ? null : getPackageMetaDataElement("Depends").toString();
        if (dependencies == null) {
            return true;
        }

        boolean ok = true;
        StringTokenizer tok = new StringTokenizer(dependencies, ",");
        while (tok.hasMoreTokens()) {
            String nextT = tok.nextToken().trim();
            String[] split = splitNameVersion(nextT);
            if (split[0].startsWith(baseSystemName.toLowerCase())) {
                // check the system version
                if (split[1] != null) {
                    if (split.length == 3) {
                        VersionPackageConstraint.VersionComparison constraint = VersionPackageConstraint.getVersionComparison(split[1]);
                        if (!VersionPackageConstraint.checkConstraint(systemVersion, constraint, split[2])) {
                            ok = false;
                            break;
                        }
                    } else {
                        // construct a "dummy" package for the base system
                        Map<String, String> baseMap = new HashMap<String, String>();
                        baseMap.put("PackageName", "weka");

                        baseMap.put("Version", systemVersion);
                        Package basePackage = new DefaultPackage(null, m_packageManager, baseMap);

                        VersionRangePackageConstraint versionRConstraint = new VersionRangePackageConstraint(basePackage);
                        VersionPackageConstraint.VersionComparison comp1 = VersionPackageConstraint.getVersionComparison(split[1]);
                        VersionPackageConstraint.VersionComparison comp2 = VersionPackageConstraint.getVersionComparison(split[3]);

                        versionRConstraint.setRangeConstraint(split[2], comp1, split[4], comp2);

                        if (!versionRConstraint.checkConstraint(basePackage)) {
                            ok = false;
                            break;
                        }
                    }
                    /*
                     * int comparisonResult = VersionPackageConstraint.compare(systemVersion,
                     * split[2]); ok = versionOK(split[1], comparisonResult); if (!ok) { break; }
                     */
                }
            }
        }

        return ok;
    }

    /**
     * Returns true if this package is good to go with the current version of the
     * software and versions of any installed packages that it depends on.
     * 
     * @return true if this package is good to go.
     * @throws Exception if a problem occurs while checking compatibility.
     */
    /*
     * public boolean isCompatible() throws Exception { // TODO rewrite this to make
     * use of getIncompatibleDependencies() // and getMissingDependencies() (IF this
     * package is installed)
     * 
     * // MIGHT just rename this method to isCompatibleWithBaseSystem
     * 
     * boolean ok = isCompatibleBaseSystem();
     * 
     * if (ok) { // check for any incompatible dependencies List<Dependency>
     * incompatible = getIncompatibleDependencies(); if (incompatible.size() > 0) {
     * ok = false; } }
     * 
     * if (ok && isInstalled()) { // first check to see if the installed version is
     * the same as us Package installedVersion =
     * m_packageManager.getInstalledPackageInfo
     * (getPackageMetaDataElement("PackageName").toString()); String
     * installedVersionS =
     * installedVersion.getPackageMetaDataElement("Version").toString(); String
     * thisVersionS = getPackageMetaDataElement("Version").toString();
     * 
     * if (VersionPackageConstraint.compare(thisVersionS, installedVersionS) ==
     * VersionPackageConstraint.VersionComparison.EQUAL) { // if equal to the
     * installed version then check to see if any dependencies are // missing. If
     * not equal to the installed version then just assume that we // we be
     * overwriting the installed version and any missing dependencies will // be
     * downloaded during the install. List<Dependency> missing =
     * getMissingDependencies(); if (missing.size() > 0) { ok = false; } } }
     * 
     * return ok; }
     */

    /**
     * Install this package.
     * 
     * @throws Exception if something goes wrong during installation.
     */
    @Override
    public void install() throws Exception {
        URL packageURL = getPackageURL();

        m_packageManager.installPackageFromURL(packageURL);
    }

    /**
     * Returns true if this package is already installed
     * 
     * @return true if this package is installed
     */
    @Override
    public boolean isInstalled() {
        File packageDir = new File(m_packageHome.getAbsoluteFile() + File.separator + m_packageMetaData.get("PackageName") + File.separator + "Description.props");
        return (packageDir.exists());
    }

    public static void main(String[] args) {
        String installed = args[0];
        String toCheckAgainst = args[1];
        String[] splitI = splitNameVersion(installed);
        String[] splitA = splitNameVersion(toCheckAgainst);

        try {

            if (splitA.length == 3) {

                System.out.println("Checking first version number against second constraint");
                VersionPackageConstraint.VersionComparison constraint = VersionPackageConstraint.getVersionComparison(splitA[1]);

                if (VersionPackageConstraint.checkConstraint(splitI[2], constraint, splitA[2])) {
                    System.out.println(splitI[2] + " is compatible with " + args[1]);
                } else {
                    System.out.println(splitI[2] + " is not compatible with " + args[1]);
                }

                Map<String, String> baseMap = new HashMap<String, String>();
                baseMap.put("PackageName", splitA[0]);
                baseMap.put("Version", splitA[2]);
                Package packageA = new DefaultPackage(null, null, baseMap);
                packageA.setPackageMetaData(baseMap);
                VersionPackageConstraint constrA = new VersionPackageConstraint(packageA);
                constrA.setVersionConstraint(constraint);

                if (splitI.length == 3) {

                    VersionPackageConstraint.VersionComparison constraintI = VersionPackageConstraint.getVersionComparison(splitI[1]);

                    Package packageI = (Package) packageA.clone();
                    packageI.setPackageMetaDataElement(VersionPackageConstraint.VERSION_KEY, splitI[2]);
                    VersionPackageConstraint constrI = new VersionPackageConstraint(packageI);
                    constrI.setVersionConstraint(constraintI);

                    PackageConstraint pc = null;
                    if ((pc = constrI.checkConstraint(constrA)) != null) {
                        System.out.println(constrI + " and " + constrA + " are compatible\n\n" + "compatible constraint " + pc);
                    } else {
                        System.out.println(constrI + " and " + constrA + " are not compatible");
                    }
                } else {
                    // TODO
                }

            } else {
                System.out.println("Checking first version number against second constraint");
                Map<String, String> baseMap = new HashMap<String, String>();
                baseMap.put("PackageName", splitI[0]);

                baseMap.put("Version", splitI[2]);
                Package p = new DefaultPackage(null, null, baseMap);

                VersionRangePackageConstraint c = new VersionRangePackageConstraint(p);

                VersionPackageConstraint.VersionComparison comp1 = VersionPackageConstraint.getVersionComparison(splitA[1]);
                VersionPackageConstraint.VersionComparison comp2 = VersionPackageConstraint.getVersionComparison(splitA[3]);
                c.setRangeConstraint(splitA[2], comp1, splitA[4], comp2);

                if (c.checkConstraint(p)) {
                    System.out.println(splitI[2] + " is compatible with " + args[1]);
                } else {
                    System.out.println(splitI[2] + " is not compatible with " + args[1]);
                }
            }

        } catch (Exception ex) {
            ex.printStackTrace();
        }

        /*
         * int compResult = VersionPackageConstraint.compare(splitI[2], splitA[2]); if
         * (versionOK(splitA[1], compResult)) { System.out.println("Compatible"); } else
         * { System.out.println("Not ok"); }
         */
    }

    /**
     * Adds a key, value pair to the meta data map.
     * 
     * @param key   the key
     * @param value the value to add
     * @throws Exception if there is no meta data map to add to.
     */
    @Override
    public void setPackageMetaDataElement(Object key, Object value) throws Exception {
        if (m_packageMetaData == null) {
            throw new Exception("[DefaultPackage] no meta data map has been set!");
        }

        // cast to Object is fine because our maps are Properties.
        Map<Object, Object> meta = (Map<Object, Object>) m_packageMetaData;

        meta.put(key, value);
    }

    @Override
    public String toString() {
        String packageName = getPackageMetaDataElement("PackageName").toString();
        String version = getPackageMetaDataElement("Version").toString();
        return packageName + " (" + version + ")";
    }
}
